iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0
Mobile Development

用 SwiftUI 魔法變出 Leetcode 刷題知識學習 App!系列 第 17

Day 17: SwiftUI 展示「Linked List」題目,如何運用 Circle、Path、MVVM

  • 分享至 

  • xImage
  •  

我們學會了 Linked List 知識後,接下來就是要結合 SwiftUI 知識一起實現這個資料結構了,如果你還沒懂鏈結串列,強烈建議你在前往上一篇複習 導讀 LeetCode 知識 - Linked List (Swift),這樣才能跟上這一篇的進度哦。

隨著越來越難的 LeetCode 知識,使用的 SwiftUI 元件也就越來越多元跟複雜,這次要實現鏈結串列,我們會使用到 CirclePathMVVM

如何建立一個鏈結

首先建立 Linked List 資料結構程式碼,因為要用 ForEach 生成,所以需要一個獨立 ID,會用 UUID 來產生。

class ListNode<T>: Identifiable {
    var id = UUID()
    var value: T
    var next: ListNode?
    
    init(_ value: T, next: ListNode? = nil) {
        self.value = value
        self.next = next
    }
}

再來要畫節點,我們用圓形 Circle 來表示一個節點,並且塗上藍色。

struct NodeView: View {
    var node: ListNode<String>
    
    var body: some View {
        HStack {
            Circle()
                .fill(Color.blue)
                .frame(width: 30, height: 30)
                .overlay(
                    Text(node.value)
                        .foregroundColor(.white)
                )
            
            if let _ = node.next {
                Arrow()
            }
        }
    }
}

Arrow 就是我們判斷下一個節點不為空的時候產生的指針。

然後利用 Path 去畫黑色三角形指針指向下一個節點。

struct Arrow: View {
    var body: some View {
        Path { path in
            path.move(to: CGPoint(x: 0, y: 0))
            path.addLine(to: CGPoint(x: 10, y: 5))
            path.addLine(to: CGPoint(x: 0, y: 10))
        }
        .fill(Color.black)
        .frame(width: 10, height: 10)
    }
}

畫出來如圖效果,我們成功建置了一個鏈結,跟一個黑色三角形指針。

struct LinkedListView: View {
    var list: [ListNode<String>]
    var body: some View {
        
        HStack(spacing: 5) {
            ForEach(list, id: \.id) { node in
                NodeView(node: node)
            }
        }.frame(width: 500, height: 500)
    }
}

最後利用 HStackForEach 去依據 ListNode 的陣列生成多個節點。

利用 MVVM 架構產出鏈結串列資料

因為資料越來越多元複雜,我們利用 MVVM 架構拆分邏輯,使其程式碼更加乾淨,M 代表 Model, V 代表 View,這裡就是 SwiftUI,VM 則是 ViewModel 專門當 Model 跟 View 溝通的橋樑,讓產出資料的來源由 ViewModel 去控制 Model ,最後更新 View。

圖片來自維基百科

所以產了一個 ViewModel ,這裡負責控制 Linked List,也就是傳進來的資料是 ListNode,然後我們把它轉換成 Array,之所以有這個轉換,是因為套用到 SwiftUI 的 ForEach 它只吃集合的資料,也就是這邊套用 Array 的話 UI 才能夠接受顯示。(筆者在這裡查了好久的資料,一直以為 ForEach 跟 for loop 運算符號沒什麼兩樣但其實能放的資料格式還是不太一樣)

class NumberViewModel: ObservableObject {
   
    @Published var resList: [ListNode<String>] = []
    
    func getLinkedList(nodes: ListNode<String>) {
        var cur: ListNode? = nodes
        var res = [ListNode<String>]()
        while(cur != nil){
            if let cur = cur {
                res.append(cur)
            }
            
            cur = cur?.next
        }
        resList = res
    }
}

要注意的是特別用了 @Published還有 ObservableObject 是為了要告訴 SwiftUI 這是可變化的變數物件,當更動資料時就要去通知 SwiftUI 讓它更新跟這個資料有關聯的 View 的 body。

最後的主程式畫面程式碼如下,我們利用點擊後,會去創立一個新的鏈結串列。

struct ContentView: View {
    @StateObject var viewModel = NumberViewModel()
    var nodes = ListNode("1", next: ListNode("2", next: ListNode("3")))
    
    var body: some View {
        VStack{
            LinkedListView(list: viewModel.resList)
        }
        Button("Add Linked List"){
            viewModel.getLinkedList(nodes: nodes)
        }
        
    }
}

@ObservedObject@StateObject

這裡使用的是 @StateObject,在學習的過程中會發現 SwiftUI 有很多這種標記,之前用最多的是 @State而這個主要是用在 Struct 的資料結構上的變數,設置他表示參數可以變動,但是資料一多的時候,會 Class 把多個資料包起來,這時候就會使用到 @StateObject,它能保證物件單次建立,而 @ObservedObject則會多次物件重新建立,在這個場景我們希望不要反覆建立物件,所以使用 @StateObject

最終效果如圖,只要按了 Add Linked List 按鈕就會使用 ViewModel 的運算,最後,我們成功在 SwiftUI 上建立了鏈結串列。

總結

時間過得很快,我們已經學習 SwiftUI 跟 LeetCode 知識超過三十天的一半天數了,隨著日子一天一天過去,我們逐漸能夠把這些內容組建成一個 App,越來越有成就感,接下來會越來越進階,在撰寫文章的時候,筆者已經感受到越來越有挑戰了,加油,如果看到這裡有任何疑問也歡迎一起討論成長。


上一篇
Day 16: 導讀 LeetCode 知識 - Linked List (Swift)
下一篇
Day 18: 導讀 LeetCode 演算法 - Backtracking (Swift)
系列文
用 SwiftUI 魔法變出 Leetcode 刷題知識學習 App!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言